package per.edward.wechatautomationutil.utils;

import android.content.Context;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import androidx.annotation.NonNull;
import per.edward.wechatautomationutil.App;

/**
 * Author Liu
 * Version 1.0
 * Description Write Log to File When systems' Log's working
 */
public class Logger {
    private int logLevel;// 输入日志类型
    private static final String logFilePath = App.extHomeDir + "log";// 日志文件在Internal Storage Or External Storage中的路径
    private static final int logKeepDays = 7;// sd卡中日志文件的最多保存天数
    private static final SimpleDateFormat logDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINESE);// 日志的输出格式
    private static final SimpleDateFormat logFileFormat = new SimpleDateFormat("yyyyMMdd", Locale.CHINESE);// 日志文件格式
    private static Boolean logEnabled = false;
    private String TAG;
    private static HashMap<String, Logger> instances = null;
    public static final LogService logService = new LogService();
    private static BlockingQueue<Journal> blockingDeque = new ArrayBlockingQueue<>(15);

    class Journal {
        String tag;
        String type;
        String message;
        Journal(String Tag, String Type, String Message) {
            tag = Tag;
            type = Type;
            message = Message;
        }
    }

    public static class LogService extends Thread {
        public void run() {
            while (logEnabled) {
                try {
                    writeLogToFile(blockingDeque.take());
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private Logger(String Tag) {
        TAG = Tag;
        logLevel = Log.WARN;
        init();
    }

    private Logger(String Tag, int LogLevel) {
        TAG = Tag;
        logLevel = LogLevel;
        init();
    }

    public static Logger getInstance(@NonNull Context context) {
        String tag = context.getClass().getSimpleName();
        return instances != null && instances.containsKey(tag) ? instances.get(tag) : new Logger(tag);
    }

    public static Logger getInstance(@NonNull Context context, int LogLevel) {
        String tag = context.getClass().getSimpleName();
        return instances != null && instances.containsKey(tag) ? instances.get(tag) : new Logger(tag, LogLevel);
    }

    private static void init() {
        if (!logEnabled) {
            File file = new File(logFilePath);
            if (!file.exists()) {
                if (file.mkdirs()) logEnabled = true;
            } else {
                logEnabled = true;
            }
        }
    }

    @SuppressWarnings("unused")
    public void setLogLevel(int level) {
        logLevel = level;
    }

    public void w(Object msg) { // 警告信息
        Log.w(TAG, msg.toString());
        if(logLevel <= Log.WARN && logEnabled) {
            try {
                blockingDeque.put(new Journal(TAG,"WARN", msg.toString()));
            } catch(InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void e(Object msg) { // 错误信息
        Log.e(TAG, msg.toString());
        if(logLevel <= Log.ERROR && logEnabled) {
            try {
                blockingDeque.put(new Journal(TAG, "ERROR", msg.toString()));
            } catch(InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void d(Object msg) {// 调试信息
        Log.d(TAG, msg.toString());
        if(logLevel <= Log.DEBUG && logEnabled) {
            try {
                blockingDeque.put(new Journal(TAG, "DEBUG", msg.toString()));
            } catch(InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void i(Object msg) {
        Log.i(TAG, msg.toString());
        if(logLevel <= Log.INFO && logEnabled) {
            try {
                blockingDeque.put(new Journal(TAG, "INFO", msg.toString()));
            } catch(InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void v(Object msg) {
        Log.v(TAG, msg.toString());
        if(logLevel <= Log.VERBOSE && logEnabled) {
            try {
                blockingDeque.put(new Journal(TAG, "VERB", msg.toString()));
            } catch(InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void w(String text) {
        Log.w(TAG, text);
        if(logLevel <= Log.WARN && logEnabled) {
            try {
                blockingDeque.put(new Journal(TAG, "WARN", text));
            } catch(InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void e(String text) {
        Log.e(TAG, text);
        if(logLevel <= Log.ERROR && logEnabled) {
            try {
                blockingDeque.put(new Journal(TAG, "ERROR", text));
            } catch(InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void d(String text) {
        Log.d(TAG, text);
        if(logLevel <= Log.DEBUG && logEnabled) {
            try {
                blockingDeque.put(new Journal(TAG, "DEBUG",  text));
            } catch(InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void i(String text) {
        Log.i(TAG, text);
        if(logLevel <= Log.INFO && logEnabled) {
            try {
                blockingDeque.put(new Journal(TAG, "INFO", text));
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void v(String text) {
        Log.v(TAG, text);
        if(logLevel <= Log.VERBOSE && logEnabled) {
            try {
                blockingDeque.put(new Journal(TAG, "VERB", text));
            } catch(InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * 打开日志文件并写入日志
     * **/
    private static void writeLogToFile(Journal journal) {
        Date nowTime = new Date();
        File logFile = new File(logFilePath, logFileFormat.format(nowTime) + ".log");
        String logMessage = logDateFormat.format(nowTime) + "|" + journal.tag + "|" + journal.type + "|" + journal.message;

        try {
            PrintWriter printWriter = new PrintWriter(logFile);
            printWriter.println(logMessage);
            printWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            delExpiredFiles();
        }
    }

    /**
     * 遍历删除制定的日志文件
     * */
    private static void delExpiredFiles() {
        File folder = new File(logFilePath);
        File[] files = folder.listFiles();
        if(files != null) {
            Pattern pattern = Pattern.compile("^\\d{8}\\.log$");
            for (File file : files) {
                String fileFullName = file.getName();
                Matcher matcher = pattern.matcher(fileFullName);
                if( ! matcher.matches() ){
                    continue;
                }
                Date dateOfFile = null;
                try {
                    dateOfFile = logFileFormat.parse(fileFullName);
                } catch (ParseException e) {
                    Log.e("Logger", "Wrong File Format: " + fileFullName);
                }
                Date dateOfDaysBefore = getDateBefore();
                if(dateOfFile != null && dateOfFile.compareTo(dateOfDaysBefore) <= 0 && file.canWrite()) {
                    file.delete();
                }
            }
        }
    }

    /**
     * 得到现在时间前的几天日期，用来得到需要删除的日志文件名
     * */
    static Date getDateBefore() {
        Date nowTime = new Date();
        Calendar beforeTime = Calendar.getInstance();
        beforeTime.setTime(nowTime);
        beforeTime.set(Calendar.DATE, beforeTime.get(Calendar.DATE) - logKeepDays);
        return beforeTime.getTime();
    }

}

